#!/bin/bash

set -eux

namespace="$(< /var/run/secrets/kubernetes.io/serviceaccount/namespace)"
readonly namespace
readonly service_domain="_$SERVICE_PORT._tcp.$SERVICE.$namespace.svc.cluster.local"
readonly sentinel_domain="_$SENTINEL_PORT._tcp.$SENTINEL.$namespace.svc.cluster.local"

redis_info () {
  set +e
  timeout 10 redis-cli -h "$1" -a "$service_domain" info replication
  set -e
}

reset_sentinel () {
  set +e
  timeout 10 redis-cli -h "$1" -p 26379 sentinel reset mymaster
  set -e
}

redis_info_role () {
  echo "$1" | grep -e '^role:' | cut -d':' -f2 | tr -d '[:space:]'
}

domain_ip () {
  /opt/bin/dig-a "$1" | head -1 | awk '{print $NF}'
}

server_domains () {
  /opt/bin/dig-srv "$1" | awk '{print $NF}' | sed 's/\.$//g'
}

# At the end of the (succeeded) script, resetting all sentinels is necessary.
# This updates the list of supervised slaves.
# If this task is omitted, the number of "supervised" slaves continues to
# increase because sentinels are unable to recognize the recovered slave
# is the same slave as the dead one.
# Kubernetes may change Pod's IP address on restart.
reset_all_sentinels () {
  local servers
  servers="$(server_domains "$sentinel_domain")"
  readonly servers
  local s
  >&2 echo "Resetting all sentinels: $servers"
  for s in $servers; do
    local s_ip
    s_ip="$(domain_ip "$s")"

    if [ -z "$s_ip" ]; then
      >&2 echo "Failed to resolve: $s"
      continue
    fi

    # Ignoring failed sentinels are allowed, since most of the sentinels are
    # expected to be alive.
    reset_sentinel "$s_ip"
  done
}

slave_priority () {
  local no
  no="$(hostname | awk -F- '{print $NF}')"
  readonly no
  local -r priority="$(((no + 1) * 10))"
  echo "slave-priority $priority"
}

# It's okay to fail during failover or other unpredictable states.
# This prevents from making things much worse.
run () {
  cp /opt/redis.template.conf /opt/redis.conf

  # Domain name of the Service is also used as the password.
  # In this case, password is just an ID to distinguish this replica set from
  # other ones in the same Kubernetes cluster.
  { 
    echo "masterauth $service_domain";
    echo "requirepass $service_domain"; 
  } >> /opt/redis.conf

  # Replica with smaller number should be the preferred candidate for Master
  # over ones with larger number.
  # This is because replicas with larger number have higher chance of being
  # removed by reducing the number of replica in a StatefulSet.
  slave_priority >> /opt/redis.conf

  # Headless Service allows newly added Redis server to scan all working servers.
  # This enables it to find if it is the first one.
  local servers
  servers="$(server_domains "$service_domain")"
  readonly servers
  local my_host
  my_host="$(hostname -f)"
  readonly my_host

  local master_ip=''

  local only_server=true
  local s
  for s in $servers; do
    # My hostname must be excluded to handle restarts.
    if [ "$s" = "$my_host" ]; then
      continue
    fi

    only_server=false

    local s_ip
    s_ip="$(domain_ip "$s")"

    if [ -z "$s_ip" ]; then
      >&2 echo "Failed to resolve: $s"
      continue
    fi

    local i
    i="$(redis_info "$s_ip")"
    if [ -n "$i" ]; then
      if [ "$(redis_info_role "$i")" = 'master' ]; then
        master_ip="$s_ip"
      fi
    else
      >&2 echo "Unable to get Replication INFO: $s ($s_ip)"
      continue
    fi
  done

  if [ "$only_server" = true ]; then
    # This is an exceptional case: if this is the first server to start in the
    # replica, this must be run as Master.
    # Otherwise the StatefulSet will be unable to start.
    :
  else
    if [ -z "$master_ip" ]; then
      >&2 echo "Unable to start because all servers are slave."
      exit 1
    fi

    # Now the Master server has been found, this server will be launched as
    # the slave of the Master.
    echo "slaveof $master_ip 6379" >> /opt/redis.conf
  fi

  reset_all_sentinels
  exec docker-entrypoint.sh redis-server "$@"
}

run "$@"
